package vulnerability

import (
	"fmt"
	"time"

	"github.com/urfave/cli/v2"

	"github.com/slimtoolkit/slim/pkg/app"
	"github.com/slimtoolkit/slim/pkg/app/master/command"
	"github.com/slimtoolkit/slim/pkg/vulnerability/epss"
)

const (
	Name  = "vulnerability"
	Usage = "Execute vulnerability related tools and operations"
	Alias = "vuln"

	EpssCmdName      = "epss"
	EpssCmdNameUsage = "Get EPPS information for the target vulnerabilities"
)

const (
	EpssOrderRecordsScoreDesc      = "score-desc"
	EpssOrderRecordsScoreAsc       = "score-asc"
	EpssOrderRecordsPercentileDesc = "percentile-desc"
	EpssOrderRecordsPercentileAsc  = "percentile-asc"
)

func IsValidOp(input string) bool {
	switch input {
	case EpssOpLookup, EpssOpList:
		return true
	}

	return false
}

func IsValidOrderRecordsValue(input string) bool {
	switch input {
	case EpssOrderRecordsScoreDesc, EpssOrderRecordsScoreAsc, EpssOrderRecordsPercentileDesc, EpssOrderRecordsPercentileAsc:
		return true
	}

	return false
}

func OrderType(input string) epss.OrderType {
	switch input {
	case EpssOrderRecordsScoreDesc:
		return epss.ScoreDescOrder
	case EpssOrderRecordsScoreAsc:
		return epss.ScoreAscOrder
	case EpssOrderRecordsPercentileDesc:
		return epss.PercentileDescOrder
	case EpssOrderRecordsPercentileAsc:
		return epss.PercentileAscOrder
	}

	return epss.NoOrder
}

func fullCmdName(subCmdName string) string {
	return fmt.Sprintf("%s.%s", Name, subCmdName)
}

type CommonCommandParams struct {
	CVEList []string
}

func CommonCommandFlagValues(ctx *cli.Context) (*CommonCommandParams, error) {
	values := &CommonCommandParams{
		CVEList: ctx.StringSlice(FlagCVE),
	}

	return values, nil
}

type EpssCommandParams struct {
	*CommonCommandParams
	Date                 time.Time
	Op                   string
	WithHistory          bool
	Limit                uint64
	Offset               uint64
	FilterCveIDPattern   string
	FilterDaysSinceAdded uint
	FilterScoreGt        float64
	FilterScoreLt        float64
	FilterPercentileGt   float64
	FilterPercentileLt   float64
	FilterOrderRecords   epss.OrderType
}

func EpssCommandFlagValues(ctx *cli.Context) (*EpssCommandParams, error) {
	common, err := CommonCommandFlagValues(ctx)
	if err != nil {
		return nil, err
	}

	values := &EpssCommandParams{
		CommonCommandParams:  common,
		Op:                   ctx.String(FlagOp),
		WithHistory:          ctx.Bool(FlagWithHistory),
		Limit:                ctx.Uint64(FlagLimit),
		Offset:               ctx.Uint64(FlagOffset),
		FilterCveIDPattern:   ctx.String(FlagFilterCveIDPattern),
		FilterDaysSinceAdded: ctx.Uint(FlagFilterDaysSinceAdded),
		FilterScoreGt:        ctx.Float64(FlagFilterScoreGt),
		FilterScoreLt:        ctx.Float64(FlagFilterScoreLt),
		FilterPercentileGt:   ctx.Float64(FlagFilterPercentileGt),
		FilterPercentileLt:   ctx.Float64(FlagFilterPercentileLt),
	}

	if !IsValidOp(values.Op) {
		return nil, fmt.Errorf("invalid operation - %s", values.Op)
	}

	if orderStr := ctx.String(FlagFilterOrderRecords); orderStr != "" {
		if !IsValidOrderRecordsValue(orderStr) {
			return nil, fmt.Errorf("invalid order records value - %s", orderStr)
		}

		values.FilterOrderRecords = OrderType(orderStr)
	}

	if dateStr := ctx.String(FlagDate); dateStr != "" {
		date, err := epss.DateFromString(dateStr)
		if err != nil {
			return nil, err
		}

		values.Date = date
	}

	return values, nil
}

var CLI = &cli.Command{
	Name:    Name,
	Aliases: []string{Alias},
	Usage:   Usage,
	Flags: []cli.Flag{
		cflag(FlagCVE),
	},
	Subcommands: []*cli.Command{
		{
			Name:  EpssCmdName,
			Usage: EpssCmdNameUsage,
			Flags: []cli.Flag{
				cflag(FlagDate),
				cflag(FlagOp),
				cflag(FlagWithHistory),
				cflag(FlagLimit),
				cflag(FlagOffset),
				cflag(FlagFilterCveIDPattern),
				cflag(FlagFilterDaysSinceAdded),
				cflag(FlagFilterScoreGt),
				cflag(FlagFilterScoreLt),
				cflag(FlagFilterPercentileGt),
				cflag(FlagFilterPercentileLt),
				cflag(FlagFilterOrderRecords),
			},
			Action: func(ctx *cli.Context) error {
				gcvalues, ok := command.CLIContextGet(ctx.Context, command.GlobalParams).(*command.GenericParams)
				if !ok || gcvalues == nil {
					return command.ErrNoGlobalParams
				}

				xc := app.NewExecutionContext(
					fullCmdName(EpssCmdName),
					gcvalues.QuietCLIMode,
					gcvalues.OutputFormat)

				cparams, err := EpssCommandFlagValues(ctx)
				xc.FailOn(err)

				if len(cparams.CVEList) == 0 && cparams.Op == EpssOpLookup {
					xc.Fail("EPSS lookup requires, at least, one CVE")
				}

				OnEpssCommand(xc, gcvalues, cparams)
				return nil
			},
		},
	},
}
